home *** CD-ROM | disk | FTP | other *** search
/ Nebula 2 / Nebula Two.iso / SourceCode / PopUpFormatter / StringList.m < prev   
Text File  |  1995-06-12  |  15KB  |  383 lines

  1. // Format: 80 columns, tabs = 4 spaces
  2. #import "StringList.h"
  3.  
  4. /******************************************************************************
  5.     StringList - by Jeff Martin
  6.  
  7.     StringList (a subclass of Storage) is a convenient way to deal with lists of character strings. It contains methods for adding strings, inserting strings, sorting strings and searching for strings.
  8.         
  9.     StringList can also add strings from a delimited string (such as the ones passed to an app by the workspace). The delimiters can be supplied or assumed to be white space.
  10.     
  11.     Finally, StringList implements a browser delegate method so that it can easily display itself if set to be a browser delegate.
  12.  
  13.     To use this object inside of InterfaceBuilder, simply drag its icon from the palettes window into the suitcases window. Then drag in a browser and set its delegate to be the stringlist. Type in some entries into StringList's inspector and test interface. The browser fills up!
  14.  
  15. Written by: Jeff Martin (jmartin@next.com)
  16. You may freely copy, distribute and reuse the code in this example.  
  17. Don't even talk to me about warranties.
  18. ******************************************************************************/
  19.     
  20. @implementation StringList : Storage
  21.  
  22. - init
  23.     // Customize Storage to be 'char *' only
  24.     [super initCount:0 elementSize:4 description:"*"];
  25.  
  26.     // StringList is assumed to be sorted by default and when emptied
  27.     //    Adding a string without sorted flag causes 'isSorted' to be set to NO
  28.     isSorted = YES;
  29.     return self; 
  30. }
  31.  
  32. - initCount: (unsigned) count
  33.     // Customize Storage to be 'char *' only
  34.     [super initCount:count elementSize:4 description:"*"];
  35.  
  36.     // StringList is assumed to be sorted by default and when emptied
  37.     //    Adding a string without sorted flag causes 'isSorted' to be set to NO
  38.     isSorted = YES;
  39.     return self; 
  40. }
  41.  
  42. /******************************************************************************
  43.     addString:, addStringIfAbsent: addStringNoCopy: addStringIfAbsentNoCopy:
  44.     addString:at:, addStringIfAbsent:at:, addStringIfAbsent:at:
  45.     addStringSorted:, addStringIfAbsentSorted:, addStringIfAbsentNoCopySorted:
  46.  
  47.     These methods are convenience methods that simply call the master addString method and provide shorter names for cleaner use assuming the defaults of ifAbsent=NO, noCopy=NO, sorted=NO and at=count. Returns the result of the main addString method (self).
  48. ******************************************************************************/
  49. - addString:(const char *)string { return 
  50.     [self addString:string ifAbsent:NO noCopy:NO sorted:NO at:[self count]];  }
  51. - addStringIfAbsent:(const char *)string { return 
  52.     [self addString:string ifAbsent:YES noCopy:NO sorted:NO at:[self count]]; }
  53. - addStringNoCopy:(const char *)string { return 
  54.     [self addString:string ifAbsent:NO noCopy:YES sorted:NO at:[self count]]; }
  55. - addStringIfAbsentNoCopy:(const char *)string { return
  56.     [self addString:string ifAbsent:YES noCopy:YES sorted:NO at:[self count]];}
  57.  
  58. - addString:(const char *)string at:(int)at;
  59. { return [self addString:string ifAbsent:NO noCopy:NO sorted:NO at:at]; }
  60. - addStringIfAbsent:(const char *)string at:(int)at;
  61. { return [self addString:string ifAbsent:YES noCopy:NO sorted:NO at:at]; }
  62. - addStringNoCopy:(const char *)string at:(int)at;
  63. { return [self addString:string ifAbsent:NO noCopy:YES sorted:NO at:at]; }
  64. - addStringIfAbsentNoCopy:(const char *)string at:(int)at;
  65. { return [self addString:string ifAbsent:YES noCopy:YES sorted:NO at:at]; }
  66.  
  67. - addStringSorted:(const char *)string;
  68. { return [self addString:string ifAbsent:NO noCopy:NO sorted:YES at:0]; }
  69. - addStringIfAbsentSorted:(const char *)string;
  70. { return [self addString:string ifAbsent:YES noCopy:NO sorted:YES at:0]; }
  71. - addStringNoCopySorted:(const char *)string;
  72. { return [self addString:string ifAbsent:NO noCopy:YES sorted:YES at:0]; }
  73. - addStringIfAbsentNoCopySorted:(const char *)string;
  74. { return [self addString:string ifAbsent:YES noCopy:YES sorted:YES at:0]; }
  75.  
  76. /******************************************************************************
  77.     addString:ifAbsent:noCopy:sorted:at:
  78.  
  79.     This method provides a flexible method for adding strings to the list. The ifAbsent flag specifies whether a string should be added if it already exists. The noCopy flag specifies whether the given string should be used (or copied). The sorted flag specifies whether the string should be added alphabetically. The at value specifies where the string should be inserted at if it is not added alphabetically. Returns self.
  80. ******************************************************************************/
  81. - addString:(const char *)string ifAbsent:(BOOL)ifAbsent noCopy:(BOOL)noCopy sorted:(BOOL)sorted at:(int)at
  82. {
  83.     BOOL stringExists;
  84.     int index = (ifAbsent || sorted)? 
  85.         [self indexOfString:string exists:&stringExists] : 0;
  86.  
  87.     // If we add only if absent and string is in list return self
  88.     if(ifAbsent && stringExists) return self;
  89.  
  90.     // If not noCopy (in other words, if copy) make copy
  91.     if(!noCopy) string = NXCopyStringBufferFromZone(string, [self zone]);
  92.  
  93.     // If sorted, get index else add at 'at'; otherwise set isSorted flag to NO
  94.     if(sorted) at = index; else isSorted = NO;
  95.     
  96.     // Add the string and return
  97.     [self insertElement:(char **)&string at:at];
  98.     return self;
  99. }
  100.  
  101. /******************************************************************************
  102.     addStrings, addStringList
  103.  
  104.     These methods allow for lists of strings to be added either from a char ** or from a StringList object. Returns self.
  105. ******************************************************************************/
  106. - addStrings:(const char *const*)strings { return 
  107.     [self addStrings:strings ifAbsent:NO noCopy:NO sorted:NO at:[self count]];}
  108. - addStrings:(const char *const*)strings ifAbsent:(BOOL)ifAbsent noCopy:(BOOL)noCopy sorted:(BOOL)sorted at:(int)at
  109. {
  110.     char **temp = (char **)strings;
  111.     
  112.     // Add each string individually, incrementing 'at' to preserve their order
  113.     while(*temp) {
  114.         [self addString:*temp ifAbsent:ifAbsent noCopy:noCopy 
  115.             sorted:sorted at:at];
  116.         at++; temp++;
  117.     }
  118.     
  119.     // If 'noCopy' then we own the memory 'strings' and should free it
  120.     if(noCopy) free((char *)strings);
  121.     
  122.     return self;
  123. }
  124.  
  125. - addStringList:stringListObject 
  126. { return [self addStrings:[stringListObject strings]]; }
  127. - addStringList:stringListObject ifAbsent:(BOOL)ifAbsent noCopy:(BOOL)noCopy sorted:(BOOL)sorted at:(int)at
  128. { return [self addStrings:[stringListObject strings] ifAbsent:ifAbsent 
  129.         noCopy:noCopy sorted:sorted at:at]; }
  130.  
  131. /******************************************************************************
  132.     addDelimitedStrings:delimiters:
  133.     addDelimitedStrings:delimiters:ifAbsent:sorted:at:
  134.  
  135.     This method takes a delimited string (like the ones passed from the workspace) and searches for the given delimiters (NULL for general whitespace). It adds each string that it finds between the delimiters using the addString method. Returns self.
  136. ******************************************************************************/
  137. - addDelimitedStrings:(const char *)string delimiters:(const char *)dels
  138. { return [self addDelimitedStrings:string delimiters:dels ifAbsent:NO  sorted:NO at:[self count]]; }
  139. - addDelimitedStrings:(const char *)string delimiters:(const char *)dels ifAbsent:(BOOL)ifAbsent sorted:(BOOL)sorted at:(int)at
  140. {
  141.     static char defaultDels[] = {' ', '\t', '\n', '\r', '\0'};
  142.     int delCount, i;
  143.     char *currChar, *currString, *stringEnd;
  144.  
  145.     // Check to see if we were handed a bogus string
  146.     if(!string || !strlen(string)) return self;
  147.  
  148.     // Check for default delimiter and get number of delimiters
  149.     if(!dels) dels = defaultDels;
  150.     delCount = strlen(dels) + 1;
  151.     
  152.     // Make copy of the string (going to use string as scratch)
  153.     string = NXCopyStringBuffer(string);
  154.     currChar = currString = (char *)string; 
  155.     stringEnd = (char *)((int)string+strlen(string));
  156.     
  157.     // Look at each character until we pass null terminator(stringEnd)
  158.     while(currChar <= stringEnd) {
  159.         
  160.         // Check the character against the delimiters
  161.         for(i=0; i<delCount; i++)
  162.  
  163.             // If current character matches a delimiter add current string
  164.             if(*currChar==dels[i]) {
  165.             
  166.                 // Set delimiter to NULL('\0')
  167.                 *currChar = '\0'; 
  168.                 
  169.                 // Only add if there is something to add. Increment at.
  170.                 if(strlen(currString))
  171.                     [self addString:currString ifAbsent:ifAbsent noCopy:NO 
  172.                         sorted:sorted at:at++];
  173.  
  174.                 // set currString to start of next string(just past curr del)
  175.                 currString = currChar + 1;
  176.                 break;
  177.             }
  178.         currChar++;
  179.     }
  180.     
  181.     free((char *)string);
  182.     return self;
  183. }
  184.  
  185.  
  186. /******************************************************************************
  187.     (const char *const*)strings
  188.     (const char *)stringAt:(int)at
  189.  
  190.     The strings methods returns all of the strings as an array of char pointers. This array is NOT null terminated.
  191.     The stringAt: method returns the string at a particular index.
  192. ******************************************************************************/
  193. - (const char *const*)strings     { return (const char *const*)dataPtr; }
  194. - (const char *)stringAt:(int)at { return *(char **)[self elementAt:at]; }
  195.  
  196.  
  197. /******************************************************************************
  198.     (BOOL)stringExists
  199.     (unsigned int)indexOfString
  200.     (unsigned int)indexOfString exists:(BOOL *)exists
  201.  
  202.     stringExists returns whether or not the string is already in the list. indexOfString returns either the strings current index or the index that it should be alphabetically(returning [self count] if not sorted).
  203.     indexOfString:stringExists: is a composite method that returns both values in about the same amount of time that it takes to compute one.
  204. ******************************************************************************/
  205. - (BOOL)stringExists:(const char *)string
  206. { BOOL exists; [self indexOfString:string exists:&exists]; return exists; }
  207.  
  208. - (unsigned)indexOfString:(const char *)string
  209. { return [self indexOfString:string exists:NULL]; }
  210.  
  211. - (unsigned)indexOfString:(const char *)string exists:(BOOL *)exists
  212. {
  213.     int index;
  214.     
  215.     // Assume the string won't be found
  216.     if(exists) *exists = NO;
  217.  
  218.     // If list is empty or no string or zero strlen, return end of list
  219.     if(![self count] || !string || !strlen(string)) return [self count];
  220.     
  221.     // If not sorted do sequential search
  222.     if(!isSorted) {
  223.         int i=0;
  224.         for(i=0; i<[self count]; i++) 
  225.             if([self stringAt:i] && (!strcasecmp(string, [self stringAt:i]))) 
  226.                 { if(exists) *exists = YES; break; }
  227.  
  228.         index = i;
  229.     }
  230.     
  231.     // Otherwise if it is sorted do a binary search
  232.     else {
  233.         int l = 0;                        // lower index
  234.         int u = [self count] - 1;        // upper index
  235.         int m = 0;                        // middle index
  236.         int guess = 0;                    // compare val.
  237.         
  238.         while(l <= u) {
  239.             m = (l+u)/2;
  240.             guess = strcasecmp([self stringAt:m], string);
  241.             
  242.             // If guess is too high, adjust the upper value
  243.             if(guess>0) u = m-1;
  244.             
  245.             // If guess is too low, adjust the lower value
  246.             else if(guess<0) l = m+1;
  247.             
  248.             // If guess is equal to string, set 'exists' flag and break
  249.             else { if(exists) *exists = YES; break; }
  250.         }
  251.  
  252.         // If last guess was right or too high index is m; if too low then m+1;
  253.         if(guess>=0) index = m; else index = m+1;
  254.     }
  255.  
  256.     return index;
  257. }
  258.  
  259.  
  260. /******************************************************************************
  261.     removeString:(const char *)string
  262.     removeStrings:(const char *const*)strings
  263.     removeStringAt:(int)at
  264.  
  265.     These methods allow for the removal of strings by value or index. Returns self.
  266. ******************************************************************************/
  267. - removeString:(const char *)string
  268. {
  269.     BOOL exists;
  270.     int index = [self indexOfString:string exists:&exists];
  271.     if(exists) [self removeStringAt:index];
  272.     return self;
  273. }
  274. - removeStrings:(const char *const*)strings
  275. {
  276.     int i;
  277.     for(i=0; i<[self count]; i++) [self removeString:strings[i]];
  278.     return self;
  279. }
  280. - (char *)removeStringAt:(int)at
  281. {
  282.     char *string = (char *)[self stringAt:at];
  283.     [self removeElementAt:at];
  284.     if(![self count]) isSorted = YES;
  285.     return string;
  286. }
  287.  
  288. /******************************************************************************
  289.     (BOOL)isSorted
  290.     sortStrings:sender
  291.     
  292.     isSorted returns whether or not the list is currently considered to be sorted. Lists are set to be sorted by default and when empty. isSorted is set to NO when a string is added without the 'sorted' flag.
  293.     sortStrings: sorts the stringList (ignoring case) and sets the sorted flag. Returns self.
  294. ******************************************************************************/
  295. - (BOOL)isSorted    { return isSorted; }
  296.  
  297. // Wrap around strcasecmp to accept 'char **' and NULL strings(NULLs to back)
  298. static int strPtrCaseCmp(const void *s1, const void *s2)
  299. {
  300.     if(*(char **)s1==*(char **)s2) return 0;
  301.     else if(!*(char **)s1) return 1; else if(!*(char **)s2) return -1;
  302.     else return strcasecmp(*((char **)s1),*((char **)s2));
  303. }
  304.  
  305. - sortStrings:sender
  306. {
  307.     qsort(dataPtr, [self count], sizeof(char *), strPtrCaseCmp); 
  308.     isSorted = YES;
  309.     return self;
  310. }
  311.  
  312. /******************************************************************************
  313.     Write and read the StringList for archiving.
  314. ******************************************************************************/
  315. - write:(NXTypedStream *)stream
  316. {    
  317.     [super write:stream];
  318.     NXWriteType(stream, "c", &isSorted);
  319.     return self;
  320. }
  321.  
  322. - read:(NXTypedStream *)stream
  323. {
  324.     [super read:stream];
  325.     NXReadType(stream, "c", &isSorted);
  326.     return self;
  327. }
  328.  
  329. /******************************************************************************
  330.     empty, freeStrings, free
  331.     
  332.     These methods empty the list, free the strings and free the list, respectively.
  333. ******************************************************************************/
  334. - empty { isSorted = YES; return [super empty]; }
  335.  
  336. - freeStrings
  337. {
  338.     while([self count]) free([self removeStringAt:0]); 
  339.     return self;
  340. }
  341.  
  342. - free
  343.     return [super free];
  344. }
  345.  
  346. /******************************************************************************
  347.     (int)browser:sender fillMatrix:matrix inColumn:(int)column
  348.     
  349.     This method is provided as a delegate method for browser to quickly display string list.
  350. ******************************************************************************/
  351. - (int)browser:sender fillMatrix:matrix inColumn:(int)column
  352. {
  353.     int   i;
  354.     id   cellList, theCell;
  355.   
  356.     // Set matrix to have the right number of cells.
  357.     [matrix renewRows:[self count] cols:1];
  358.  
  359.     // Get list of cells from the matrix.
  360.     cellList = [matrix cellList];
  361.  
  362.     // For each cell set its value, set whether it is a leaf or not and 
  363.     //   mark it loaded.
  364.     for(i=0;i<[cellList count];i++) {
  365.         theCell = [cellList objectAt:i];
  366.         [theCell setStringValue:[self stringAt:i]];
  367.         [theCell setLeaf:YES];
  368.         [theCell setLoaded:YES];
  369.     }
  370.  
  371.     // Return the number of rows.
  372.     return [self count];
  373. }
  374.  
  375. // Interface Builder support
  376. - (const char *)getInspectorClassName { return "SLInspector"; }
  377.  
  378. @end
  379.  
  380. // Fixed indexOf: bug. Thanks Stefanie Herzer!